package com.nexgo.data.repository;

import android.annotation.SuppressLint;
import android.support.annotation.NonNull;

import com.nexgo.data.entity.mapper.EntityDataMapper;
import com.nexgo.data.exception.RequestException;
import com.nexgo.data.net.HttpsComm;
import com.nexgo.domain.repository.PaymentPlatform;
import com.nexgo.iso8583.exception.WriteException;
import com.nexgo.payment.entity.MessageData;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.HttpException;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

/**
 * Created by xiaox on 2017/12/13.
 */

public class HttpsPayment implements PaymentPlatform {
    private Logger mLog = LoggerFactory.getLogger(HttpsPayment.class.getSimpleName());
    private static final String KEY_STORE_TYPE = "BKS";
    private static final String KEY_STORE_PATH = "/assets/cupcert.bks";
    private static final String PROTOCOL_TYPE = "TLS";
    private static final String CERTIFICATE_FORMAT = "X509";
    private HttpsComm mHttpsComm;
    private X509TrustManager mTrustManager;

    public HttpsPayment(String ip, int port, int timeout) {
        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor()
                .setLevel(HttpLoggingInterceptor.Level.BODY);
        okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(timeout, TimeUnit.SECONDS)
                .writeTimeout(timeout, TimeUnit.SECONDS)
                .readTimeout(timeout, TimeUnit.SECONDS)
                .addInterceptor(logInterceptor)
                .sslSocketFactory(createSSLSocketFactory())
                .hostnameVerifier(new TrustAllHostnameVerifier());
                /*.sslSocketFactory(getSocketFactory(), mTrustManager)
                .hostnameVerifier((hostname, session) -> true);*/
        try {
            Retrofit retrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .baseUrl(ip)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
            mHttpsComm = retrofit.create(HttpsComm.class);
        } catch (Exception e) {
            mLog.debug("Retrofit初始化异常");
            e.printStackTrace();
        } finally {
        }
    }
//xhj add
    /**
     * 默认信任所有的证书
     * TODO 最好加上证书认证，主流App都有自己的证书
     *
     * @return
     */
    @SuppressLint("TrulyRandom")
    private static SSLSocketFactory createSSLSocketFactory() {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()},
                    new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }

        return sSLSocketFactory;
    }
    //xhj add
    private static class TrustAllManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)

                throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
    //xhj add
    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
    private SSLSocketFactory getSocketFactory() {
        try {
            SSLContext ssl = SSLContext.getInstance(PROTOCOL_TYPE);
            TrustManager[] trustManagers = getTrustManagers(getKeyStore());
            for (TrustManager trustManager : trustManagers) {
                if (trustManager instanceof X509TrustManager) {
                    mTrustManager = (X509TrustManager) trustManager;
                }
            }
            ssl.init(null, trustManagers, new SecureRandom());
            return ssl.getSocketFactory();
        } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | KeyManagementException | IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private TrustManager[] getTrustManagers(KeyStore ks) throws NoSuchAlgorithmException, KeyStoreException {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(CERTIFICATE_FORMAT);
        tmf.init(ks);
        return tmf.getTrustManagers();
    }

    @NonNull
    private KeyStore getKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        InputStream ksIn = getClass().getResourceAsStream(KEY_STORE_PATH);
        KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE);
        ks.load(ksIn, null);
        ksIn.close();
        return ks;
    }

    @Override
    public Observable<Object> trans(Object params) {
        final EntityDataMapper mapper = new EntityDataMapper();
        return Observable.create(emitter -> mHttpsComm.sendAndRecv(RequestBody.create(MediaType.parse("application/json"),
                mapper.transform(params)),((MessageData) params).getJsonSendData().getREQ_HEAD().getTranscode()+".do")
                .map(responseBody -> mapper.transform(responseBody.source().readByteArray()))
                .subscribe(recvData -> emitter.onNext(recvData),
                        throwable -> onErrorMap(emitter, throwable),
                        () -> emitter.onComplete()));
    }

    /**
     * 抛错转换
     * @param emitter
     * @param throwable
     */
    private void onErrorMap(ObservableEmitter<Object> emitter, Throwable throwable) {
        if (throwable instanceof WriteException) {
            emitter.onError(new RequestException("组包失败"));
        } else if (throwable instanceof SocketTimeoutException || throwable instanceof ConnectException|| throwable instanceof HttpException) {
            emitter.onError(new RequestException("网络连接失败"));
        } else {
            emitter.onError(throwable);
        }
    }


}
